Bug#1053219: bookworm-pu: package lemonldap-ng/2.16.1+ds-deb12u2

Yadd yadd at debian.org
Fri Sep 29 14:37:25 BST 2023


Package: release.debian.org
Severity: normal
Tags: bookworm
User: release.debian.org at packages.debian.org
Usertags: pu
X-Debbugs-Cc: lemonldap-ng at packages.debian.org, yadd at debian.org
Control: affects -1 + src:lemonldap-ng

[ Reason ]
Two new vulnerabilities have been dicovered and fixed in lemonldap-ng:
 - an open redirection only when configuration is edited by hand and
   doesn't follow OIDC specifications
 - a server-side-request-forgery (CVE-2023-44469) in OIDC protocol:
   A little-know feature of OIDC allows the OpenID Provider to fetch the
   Authorization request parameters itself by indicating a request_uri
   parameter. This feature is now restricted to a white list using this
   patch

[ Impact ]
One low and one medium security issue.

[ Tests ]
Patches includes test updates

[ Risks ]
Outside of test changes, patches are not so big and the test coverage
provided by upstream is good, so risk is moderate.

[ Checklist ]
  [X] *all* changes are documented in the d/changelog
  [X] I reviewed all changes and I approve them
  [X] attach debdiff against the package in (old)stable
  [X] the issue is verified as fixed in unstable

[ Changes ]
- open redirection patch: just rejects requests with `redirect_uri` if
  relying party configuration has no declared redirect URIs.
- SSRF patch:
  * add new configuration parameter to list authorized "request_uris"
  * change the algorithm that manage request_uri parameter

Cheers,
Xavier
-------------- next part --------------
diff --git a/debian/NEWS b/debian/NEWS
index b8955920b..5295a3cbb 100644
--- a/debian/NEWS
+++ b/debian/NEWS
@@ -1,3 +1,13 @@
+lemonldap-ng (2.16.1+ds-deb12u2) bullseye; urgency=medium
+
+  A little-know feature of OIDC allows the OpenID Provider to fetch the
+  Authorization request parameters itself by indicating a request_uri
+  parameter.
+  By default, this feature is now restricted to a white list. See
+  Relying-Party security option to fill this field.
+
+ -- Yadd <yadd at debian.org>  Fri, 29 Sep 2023 17:15:03 +0400
+
 lemonldap-ng (2.0.9+ds-1) unstable; urgency=medium
 
   CVE-2020-24660
diff --git a/debian/changelog b/debian/changelog
index cd4c8a023..148164a94 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+lemonldap-ng (2.16.1+ds-deb12u2) bookworm; urgency=medium
+
+  * Fix open redirection when OIDC RP has no redirect uris
+  * Fix Server-Side-Request-Forgery issue in OIDC (CVE-2023-44469)
+
+ -- Yadd <yadd at debian.org>  Fri, 29 Sep 2023 17:18:12 +0400
+
 lemonldap-ng (2.16.1+ds-deb12u1) bookworm; urgency=medium
 
   * Apply login control to auth-slave requests
diff --git a/debian/patches/SSRF-issue.patch b/debian/patches/SSRF-issue.patch
new file mode 100644
index 000000000..3c6ca8b51
--- /dev/null
+++ b/debian/patches/SSRF-issue.patch
@@ -0,0 +1,795 @@
+Description: fix SSRF vulnerability
+ Issue described here: https://security.lauritz-holtmann.de/post/sso-security-ssrf/
+Author: Maxime Besson <maxime.besson at worteks.com>
+Origin: upstream, https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/merge_requests/383/diffs
+Bug: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/issues/2998
+Forwarded: not-needed
+Applied-Upstream: 2.17.1, https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/merge_requests/383/diffs
+Reviewed-By: Yadd <yadd at debian.org>
+Last-Update: 2023-09-22
+
+--- a/doc/sources/admin/idpopenidconnect.rst
++++ b/doc/sources/admin/idpopenidconnect.rst
+@@ -247,6 +247,11 @@
+       This feature only works if you have configured a form-based authentication module.
+    -  **Allow OAuth2.0 Client Credentials Grant** (since version ``2.0.11``): Allow the use of the
+       :ref:`Client Credentials Grant <client-credentials-grant>` by this client.
++   -  **Allowed URLs for fetching Request Object**: (since version ``2.17.1``):
++      which URLs may be called by the portal to fetch the request object (see
++      `request_uri
++      <https://openid.net/specs/openid-connect-core-1_0.html#RequestUriParameter>`__
++      in OIDC specifications). These URLs may use wildcards (``https://app.example.com/*``).
+    -  **Authentication level**: Required authentication level to access this application
+    -  **Access rule**: Lets you specify a :doc:`Perl rule<rules_examples>` to restrict access to this client
+ 
+--- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm
++++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm
+@@ -4656,6 +4656,7 @@
+         oidcRPMetaDataOptionsComment                  => { type => 'longtext' },
+         oidcRPMetaDataOptionsOfflineSessionExpiration => { type => 'int' },
+         oidcRPMetaDataOptionsRedirectUris             => { type => 'text', },
++        oidcRPMetaDataOptionsRequestUris              => { type => 'text', },
+         oidcRPMetaDataOptionsExtraClaims              => {
+             type    => 'keyTextContainer',
+             keyTest => qr/^[\x21\x23-\x5B\x5D-\x7E]+$/,
+--- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm
++++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm
+@@ -255,6 +255,7 @@
+                             'oidcRPMetaDataOptionsAllowOffline',
+                             'oidcRPMetaDataOptionsAllowPasswordGrant',
+                             'oidcRPMetaDataOptionsAllowClientCredentialsGrant',
++                            'oidcRPMetaDataOptionsRequestUris',
+                             'oidcRPMetaDataOptionsAuthnLevel',
+                             'oidcRPMetaDataOptionsRule',
+                         ]
+--- a/lemonldap-ng-manager/site/htdocs/static/languages/ar.json
++++ b/lemonldap-ng-manager/site/htdocs/static/languages/ar.json
+@@ -726,6 +726,7 @@
+ "oidcRPMetaDataOptionsPublic":"Public client",
+ "oidcRPMetaDataOptionsRedirectUris":"?????? ????? ??????? ??????? ??? ?????? ??????",
+ "oidcRPMetaDataOptionsRefreshToken":"Use refresh tokens",
++"oidcRPMetaDataOptionsRequestUris":"Allowed URLs for fetching Request Object",
+ "oidcRPMetaDataOptionsRequirePKCE":"Require PKCE",
+ "oidcRPMetaDataOptionsRule":"????? ??????",
+ "oidcRPMetaDataOptionsScopes":"????",
+--- a/lemonldap-ng-manager/site/htdocs/static/languages/en.json
++++ b/lemonldap-ng-manager/site/htdocs/static/languages/en.json
+@@ -726,6 +726,7 @@
+ "oidcRPMetaDataOptionsPublic":"Public client",
+ "oidcRPMetaDataOptionsRedirectUris":"Allowed redirection addresses for login",
+ "oidcRPMetaDataOptionsRefreshToken":"Use refresh tokens",
++"oidcRPMetaDataOptionsRequestUris":"Allowed URLs for fetching Request Object",
+ "oidcRPMetaDataOptionsRequirePKCE":"Require PKCE",
+ "oidcRPMetaDataOptionsRule":"Access rule",
+ "oidcRPMetaDataOptionsScopes":"Scope",
+--- a/lemonldap-ng-manager/site/htdocs/static/languages/es.json
++++ b/lemonldap-ng-manager/site/htdocs/static/languages/es.json
+@@ -726,6 +726,7 @@
+ "oidcRPMetaDataOptionsPublic":"Cliente p?blico",
+ "oidcRPMetaDataOptionsRedirectUris":"Allowed redirection addresses for login",
+ "oidcRPMetaDataOptionsRefreshToken":"Use refresh tokens",
++"oidcRPMetaDataOptionsRequestUris":"Allowed URLs for fetching Request Object",
+ "oidcRPMetaDataOptionsRequirePKCE":"Se requiere PKCE",
+ "oidcRPMetaDataOptionsRule":"Regla de acceso",
+ "oidcRPMetaDataOptionsScopes":"?mbito",
+--- a/lemonldap-ng-manager/site/htdocs/static/languages/fr.json
++++ b/lemonldap-ng-manager/site/htdocs/static/languages/fr.json
+@@ -726,6 +726,7 @@
+ "oidcRPMetaDataOptionsPublic":"Client public",
+ "oidcRPMetaDataOptionsRedirectUris":"Adresses de redirection autoris?es pour la connexion",
+ "oidcRPMetaDataOptionsRefreshToken":"Utiliser les jetons de renouvellement",
++"oidcRPMetaDataOptionsRequestUris":"URLs autoris?es pour r?cup?rer les param?tres de la requ?te",
+ "oidcRPMetaDataOptionsRequirePKCE":"PKCE requis",
+ "oidcRPMetaDataOptionsRule":"R?gle d'acc?s",
+ "oidcRPMetaDataOptionsScopes":"Scope",
+--- a/lemonldap-ng-manager/site/htdocs/static/languages/he.json
++++ b/lemonldap-ng-manager/site/htdocs/static/languages/he.json
+@@ -726,6 +726,7 @@
+ "oidcRPMetaDataOptionsPublic":"???? ??????",
+ "oidcRPMetaDataOptionsRedirectUris":"Allowed redirection addresses for login",
+ "oidcRPMetaDataOptionsRefreshToken":"?????? ???????? ?????",
++"oidcRPMetaDataOptionsRequestUris":"Allowed URLs for fetching Request Object",
+ "oidcRPMetaDataOptionsRequirePKCE":"????? PKCE",
+ "oidcRPMetaDataOptionsRule":"??? ????",
+ "oidcRPMetaDataOptionsScopes":"????",
+--- a/lemonldap-ng-manager/site/htdocs/static/languages/it.json
++++ b/lemonldap-ng-manager/site/htdocs/static/languages/it.json
+@@ -726,6 +726,7 @@
+ "oidcRPMetaDataOptionsPublic":"Cliente pubblico",
+ "oidcRPMetaDataOptionsRedirectUris":"Indirizzi di reindirizzazione consentiti per l'accesso",
+ "oidcRPMetaDataOptionsRefreshToken":"Use refresh tokens",
++"oidcRPMetaDataOptionsRequestUris":"Allowed URLs for fetching Request Object",
+ "oidcRPMetaDataOptionsRequirePKCE":"Richiedi PKCE",
+ "oidcRPMetaDataOptionsRule":"Regola di accesso",
+ "oidcRPMetaDataOptionsScopes":"Scopo",
+--- a/lemonldap-ng-manager/site/htdocs/static/languages/pl.json
++++ b/lemonldap-ng-manager/site/htdocs/static/languages/pl.json
+@@ -726,6 +726,7 @@
+ "oidcRPMetaDataOptionsPublic":"Klient publiczny",
+ "oidcRPMetaDataOptionsRedirectUris":"Dozwolone adresy przekierowa? dla logowania",
+ "oidcRPMetaDataOptionsRefreshToken":"U?yj token?w od?wie?aj?cych",
++"oidcRPMetaDataOptionsRequestUris":"Allowed URLs for fetching Request Object",
+ "oidcRPMetaDataOptionsRequirePKCE":"Wymagaj PKCE",
+ "oidcRPMetaDataOptionsRule":"Regu?a dost?pu",
+ "oidcRPMetaDataOptionsScopes":"Zakres",
+--- a/lemonldap-ng-manager/site/htdocs/static/languages/pt.json
++++ b/lemonldap-ng-manager/site/htdocs/static/languages/pt.json
+@@ -726,6 +726,7 @@
+ "oidcRPMetaDataOptionsPublic":"Cliente p?blico",
+ "oidcRPMetaDataOptionsRedirectUris":"Endere?os de redirecionamento permitidos para o login",
+ "oidcRPMetaDataOptionsRefreshToken":"Usar tokens renovados",
++"oidcRPMetaDataOptionsRequestUris":"Allowed URLs for fetching Request Object",
+ "oidcRPMetaDataOptionsRequirePKCE":"Exigir PKCE",
+ "oidcRPMetaDataOptionsRule":"Regra de acesso",
+ "oidcRPMetaDataOptionsScopes":"Escopo",
+--- a/lemonldap-ng-manager/site/htdocs/static/languages/pt_BR.json
++++ b/lemonldap-ng-manager/site/htdocs/static/languages/pt_BR.json
+@@ -726,6 +726,7 @@
+ "oidcRPMetaDataOptionsPublic":"Cliente p?blico",
+ "oidcRPMetaDataOptionsRedirectUris":"Endere?os de redirecionamento permitidos para o login",
+ "oidcRPMetaDataOptionsRefreshToken":"Usar tokens renovados",
++"oidcRPMetaDataOptionsRequestUris":"Allowed URLs for fetching Request Object",
+ "oidcRPMetaDataOptionsRequirePKCE":"Exigir PKCE",
+ "oidcRPMetaDataOptionsRule":"Regra de acesso",
+ "oidcRPMetaDataOptionsScopes":"Escopo",
+--- a/lemonldap-ng-manager/site/htdocs/static/languages/tr.json
++++ b/lemonldap-ng-manager/site/htdocs/static/languages/tr.json
+@@ -726,6 +726,7 @@
+ "oidcRPMetaDataOptionsPublic":"A??k istemci",
+ "oidcRPMetaDataOptionsRedirectUris":"Giri? i?in izin verilen y?nlendirme adresleri",
+ "oidcRPMetaDataOptionsRefreshToken":"Yeni jetonlar? kullan",
++"oidcRPMetaDataOptionsRequestUris":"Allowed URLs for fetching Request Object",
+ "oidcRPMetaDataOptionsRequirePKCE":"PKCE gerektir",
+ "oidcRPMetaDataOptionsRule":"Eri?im kural?",
+ "oidcRPMetaDataOptionsScopes":"Kapsam",
+--- a/lemonldap-ng-manager/site/htdocs/static/languages/vi.json
++++ b/lemonldap-ng-manager/site/htdocs/static/languages/vi.json
+@@ -726,6 +726,7 @@
+ "oidcRPMetaDataOptionsPublic":"Kh?ch h?ng c?ng khai",
+ "oidcRPMetaDataOptionsRedirectUris":"C?c ??a ch? chuy?n h??ng ???c ph?p ?? ??ng nh?p",
+ "oidcRPMetaDataOptionsRefreshToken":"S? d?ng m? th?ng b?o l?m m?i",
++"oidcRPMetaDataOptionsRequestUris":"Allowed URLs for fetching Request Object",
+ "oidcRPMetaDataOptionsRequirePKCE":"Y?u c?u PKCE",
+ "oidcRPMetaDataOptionsRule":"Quy t?c truy c?p",
+ "oidcRPMetaDataOptionsScopes":"Ph?m vi",
+--- a/lemonldap-ng-manager/site/htdocs/static/languages/zh.json
++++ b/lemonldap-ng-manager/site/htdocs/static/languages/zh.json
+@@ -726,6 +726,7 @@
+ "oidcRPMetaDataOptionsPublic":"?????",
+ "oidcRPMetaDataOptionsRedirectUris":"???????????",
+ "oidcRPMetaDataOptionsRefreshToken":"????????",
++"oidcRPMetaDataOptionsRequestUris":"Allowed URLs for fetching Request Object",
+ "oidcRPMetaDataOptionsRequirePKCE":"?? PKCE",
+ "oidcRPMetaDataOptionsRule":"????",
+ "oidcRPMetaDataOptionsScopes":"??",
+--- a/lemonldap-ng-manager/site/htdocs/static/languages/zh_TW.json
++++ b/lemonldap-ng-manager/site/htdocs/static/languages/zh_TW.json
+@@ -726,6 +726,7 @@
+ "oidcRPMetaDataOptionsPublic":"?????",
+ "oidcRPMetaDataOptionsRedirectUris":"???????????",
+ "oidcRPMetaDataOptionsRefreshToken":"????????",
++"oidcRPMetaDataOptionsRequestUris":"Allowed URLs for fetching Request Object",
+ "oidcRPMetaDataOptionsRequirePKCE":"?? PKCE",
+ "oidcRPMetaDataOptionsRule":"????",
+ "oidcRPMetaDataOptionsScopes":"??",
+--- a/lemonldap-ng-portal/MANIFEST
++++ b/lemonldap-ng-portal/MANIFEST
+@@ -656,6 +656,7 @@
+ t/32-OIDC-Password-Grant.t
+ t/32-OIDC-Refresh-Token.t
+ t/32-OIDC-Register.t
++t/32-OIDC-Request-Uri.t
+ t/32-OIDC-Response-Modes.t
+ t/32-OIDC-RP-rule.t
+ t/32-OIDC-Token-Exchange.t
+--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/OpenIDConnect.pm
++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/OpenIDConnect.pm
+@@ -208,31 +208,97 @@
+                 return PE_ERROR;
+             }
+ 
+-            # Extract request_uri/request parameter
+-            if ( $oidc_request->{'request_uri'} ) {
+-                my $request =
+-                  $self->getRequestJWT( $oidc_request->{'request_uri'} );
++            # Client ID must be provided and cannot come from
++            # request or request_uri
++            unless ( $oidc_request->{'client_id'} ) {
++                $self->logger->error("Client ID is required");
++                return PE_ERROR;
++            }
++
++            # Check client_id
++            my $client_id = $oidc_request->{'client_id'};
++            $self->logger->debug("Request from client id $client_id");
++
++            # Verify that client_id is registered in configuration
++            my $rp = $self->getRP($client_id);
++
++            unless ($rp) {
++                $self->logger->error(
++                        "No registered Relying Party found with"
++                      . " client_id $client_id" );
++                return PE_UNKNOWNPARTNER;
++            }
++            else {
++                $self->logger->debug("Client id $client_id matches RP $rp");
++            }
++
++            # Scope must be provided and cannot come from request or request_uri
++            unless ( $oidc_request->{'scope'} ) {
++                $self->logger->error("Scope is required");
++                return PE_ERROR;
++            }
+ 
+-                if ($request) {
+-                    $oidc_request->{'request'} = $request;
++            # Extract request_uri/request parameter
++            if ( my $request_uri = $oidc_request->{'request_uri'} ) {
++                if (
++                    $self->isUriAllowedForRP(
++                        $request_uri,                       $rp,
++                        "oidcRPMetaDataOptionsRequestUris", 1
++                    )
++                  )
++                {
++                    my $request = $self->getRequestJWT($request_uri);
++                    if ($request) {
++                        $oidc_request->{'request'} = $request;
++                    }
++                    else {
++                        $self->logger->error(
++                            "Error with Request URI resolution");
++                        return PE_ERROR;
++                    }
+                 }
+                 else {
+-                    $self->logger->error("Error with Request URI resolution");
++                    $self->logger->error(
++                        "Request URI $request_uri is not allowed for $rp");
+                     return PE_ERROR;
+                 }
+             }
+ 
+             if ( $oidc_request->{'request'} ) {
+-                my $request = getJWTPayload( $oidc_request->{'request'} );
++                if (
++                    $self->verifyJWTSignature(
++                        $oidc_request->{'request'},
++                        undef, $rp
++                    )
++                  )
++                {
++                    $self->logger->debug("JWT signature request verified");
++                    my $request = getJWTPayload( $oidc_request->{'request'} );
+ 
+-                # Override OIDC parameters by request content
+-                foreach ( keys %$request ) {
+-                    $self->logger->debug(
+-"Override $_ OIDC param by value present in request parameter"
+-                    );
+-                    $oidc_request->{$_} = $request->{$_};
+-                    $self->p->setHiddenFormValue( $req, $_, $request->{$_}, '',
+-                        0 );
++                    # Override OIDC parameters by request content
++                    foreach ( keys %$request ) {
++                        $self->logger->debug( "Override $_ OIDC param"
++                              . " by value present in request parameter" );
++
++                        if ( $_ eq "client_id" or $_ eq "response_type" ) {
++                            if ( $oidc_request->{$_} ne $request->{$_} ) {
++                                $self->logger->error( "$_ from request JWT ("
++                                      . $oidc_request->{$_}
++                                      . ") does not match $_ from request URI ("
++                                      . $request->{$_}
++                                      . ")" );
++                                return PE_ERROR;
++                            }
++                        }
++                        $oidc_request->{$_} = $request->{$_};
++                        $self->p->setHiddenFormValue( $req, $_, $request->{$_},
++                            '', 0 );
++                    }
++                }
++                else {
++                    $self->logger->error(
++                        "JWT signature request can not be verified");
++                    return PE_ERROR;
+                 }
+             }
+ 
+@@ -241,37 +307,12 @@
+                 $self->logger->error("Redirect URI is required");
+                 return PE_ERROR;
+             }
+-            unless ( $oidc_request->{'scope'} ) {
+-                $self->logger->error("Scope is required");
+-                return PE_ERROR;
+-            }
+-            unless ( $oidc_request->{'client_id'} ) {
+-                $self->logger->error("Client ID is required");
+-                return PE_ERROR;
+-            }
+             if ( $flow eq "implicit" and not defined $oidc_request->{'nonce'} )
+             {
+                 $self->logger->error("Nonce is required for implicit flow");
+                 return PE_ERROR;
+             }
+ 
+-            # Check client_id
+-            my $client_id = $oidc_request->{'client_id'};
+-            $self->logger->debug("Request from client id $client_id");
+-
+-            # Verify that client_id is registered in configuration
+-            my $rp = $self->getRP($client_id);
+-
+-            unless ($rp) {
+-                $self->logger->error(
+-                        "No registered Relying Party found with"
+-                      . " client_id $client_id" );
+-                return PE_UNKNOWNPARTNER;
+-            }
+-            else {
+-                $self->logger->debug("Client id $client_id matches RP $rp");
+-            }
+-
+             # Check if this RP is authorized
+             if ( my $rule = $self->spRules->{$rp} ) {
+                 my $ruleVariables =
+@@ -290,24 +331,14 @@
+ 
+             # Check redirect_uri
+             my $redirect_uri  = $oidc_request->{'redirect_uri'};
+-            my $redirect_uris = $self->conf->{oidcRPMetaDataOptions}->{$rp}
+-              ->{oidcRPMetaDataOptionsRedirectUris};
+-
+-            if ($redirect_uris) {
+-                my $redirect_uri_allowed = 0;
+-                foreach ( split( /\s+/, $redirect_uris ) ) {
+-                    $redirect_uri_allowed = 1 if $redirect_uri eq $_;
+-                }
+-                unless ($redirect_uri_allowed) {
+-                    $self->userLogger->error(
+-                        "Redirect URI $redirect_uri not allowed");
+-                    return PE_UNAUTHORIZEDURL;
+-                }
+-            }
+-            elsif ($redirect_uri) {
+-                $self->logger->error(
+-"RP $rp has no RedirectUris, unable to handle accept redirect_uri=$redirect_uri"
+-                );
++            if (
++                !$self->isUriAllowedForRP(
++                    $redirect_uri, $rp, 'oidcRPMetaDataOptionsRedirectUris'
++                )
++              )
++            {
++                $self->userLogger->error(
++                    "Redirect URI $redirect_uri not allowed");
+                 return PE_UNAUTHORIZEDURL;
+             }
+ 
+@@ -411,24 +442,6 @@
+                 return PE_ERROR;
+             }
+ 
+-            # Check Request JWT signature
+-            if ( $oidc_request->{'request'} ) {
+-                unless (
+-                    $self->verifyJWTSignature(
+-                        $oidc_request->{'request'},
+-                        undef, $rp
+-                    )
+-                  )
+-                {
+-                    $self->logger->error(
+-                        "JWT signature request can not be verified");
+-                    return PE_ERROR;
+-                }
+-                else {
+-                    $self->logger->debug("JWT signature request verified");
+-                }
+-            }
+-
+             # Check id_token_hint
+             my $id_token_hint = $oidc_request->{'id_token_hint'};
+             if ($id_token_hint) {
+@@ -1067,26 +1080,13 @@
+ 
+                 if ($post_logout_redirect_uri) {
+ 
+-                    # Check redirect URI is allowed
+-                    my $redirect_uri_allowed = 0;
+-                    foreach ( keys %{ $self->conf->{oidcRPMetaDataOptions} } ) {
+-                        my $logout_rp = $_;
+-                        if ( my $redirect_uris =
+-                            $self->conf->{oidcRPMetaDataOptions}->{$logout_rp}
+-                            ->{oidcRPMetaDataOptionsPostLogoutRedirectUris} )
+-                        {
+-                            foreach ( split( /\s+/, $redirect_uris ) ) {
+-                                if ( $post_logout_redirect_uri eq $_ ) {
+-                                    $self->logger->debug(
+-"$post_logout_redirect_uri is an allowed logout redirect URI for RP $logout_rp"
+-                                    );
+-                                    $redirect_uri_allowed = 1;
+-                                }
+-                            }
+-                        }
+-                    }
+-
+-                    unless ($redirect_uri_allowed) {
++                    unless (
++                        $self->findRPFromUri(
++                            $post_logout_redirect_uri,
++                            'oidcRPMetaDataOptionsPostLogoutRedirectUris'
++                        )
++                      )
++                    {
+                         $self->logger->error(
+                             "$post_logout_redirect_uri is not allowed");
+                         return PE_UNAUTHORIZEDURL;
+@@ -1118,6 +1118,43 @@
+     return PE_ERROR;
+ }
+ 
++sub findRPFromUri {
++    my ( $self, $uri, $option ) = @_;
++
++    my $found_rp;
++    foreach my $rp ( keys %{ $self->conf->{oidcRPMetaDataOptions} } ) {
++        $found_rp = $rp if $self->isUriAllowedForRP( $uri, $rp, $option );
++    }
++    return $found_rp;
++}
++
++sub isUriAllowedForRP {
++    my ( $self, $uri, $rp, $option, $wildcard_allowed ) = @_;
++    my $allowed_uris = $self->conf->{oidcRPMetaDataOptions}->{$rp}->{$option} // "";
++
++    my $is_uri_allowed;
++    if ($wildcard_allowed) {
++        $is_uri_allowed =
++          grep { _wildcard_match( $_, $uri ) } split( /\s+/, $allowed_uris );
++    }
++    else {
++        $is_uri_allowed = grep { $_ eq $uri } split( /\s+/, $allowed_uris );
++    }
++    return $is_uri_allowed;
++}
++
++sub _wildcard_match {
++    my ( $config_url, $candidate ) = @_;
++
++    # Quote everything
++    my $config_re = $config_url =~ s/(.)/\Q$1/gr;
++
++    # Replace \* by .*
++    $config_re =~ s/\\\*/.*/g;
++
++    return ( $candidate =~ qr/^$config_re$/ ? 1 : 0 );
++}
++
+ # Handle token endpoint
+ sub token {
+     my ( $self, $req ) = @_;
+@@ -2037,6 +2074,7 @@
+     my $userinfo_signed_response_alg =
+       $client_metadata->{userinfo_signed_response_alg};
+     my $redirect_uris = $client_metadata->{redirect_uris};
++    my $request_uris  = $client_metadata->{request_uris};
+ 
+     # Register RP in global configuration
+     my $conf = $self->confAcc->getConf( { raw => 1, noCache => 1 } );
+@@ -2058,6 +2096,9 @@
+       = $id_token_signed_response_alg;
+     $conf->{oidcRPMetaDataOptions}->{$rp}->{oidcRPMetaDataOptionsRedirectUris}
+       = join( ' ', @$redirect_uris );
++    $conf->{oidcRPMetaDataOptions}->{$rp}->{oidcRPMetaDataOptionsRequestUris} =
++      join( ' ', @$request_uris )
++      if $request_uris and @$request_uris;
+     $conf->{oidcRPMetaDataOptions}->{$rp}
+       ->{oidcRPMetaDataOptionsUserInfoSignAlg} = $userinfo_signed_response_alg
+       if defined $userinfo_signed_response_alg;
+@@ -2095,6 +2136,8 @@
+         $registration_response->{'id_token_signed_response_alg'} =
+           $id_token_signed_response_alg;
+         $registration_response->{'redirect_uris'} = $redirect_uris;
++        $registration_response->{'request_uris'}  = $request_uris
++          if $request_uris and @$request_uris;
+         $registration_response->{'userinfo_signed_response_alg'} =
+           $userinfo_signed_response_alg
+           if defined $userinfo_signed_response_alg;
+@@ -2121,25 +2164,13 @@
+ 
+     if ($post_logout_redirect_uri) {
+ 
+-        # Check redirect URI is allowed
+-        my $redirect_uri_allowed = 0;
+-        foreach ( keys %{ $self->conf->{oidcRPMetaDataOptions} } ) {
+-            my $logout_rp = $_;
+-            my $redirect_uris =
+-              $self->conf->{oidcRPMetaDataOptions}->{$logout_rp}
+-              ->{oidcRPMetaDataOptionsPostLogoutRedirectUris};
+-
+-            foreach ( split( /\s+/, $redirect_uris ) ) {
+-                if ( $post_logout_redirect_uri eq $_ ) {
+-                    $self->logger->debug(
+-"$post_logout_redirect_uri is an allowed logout redirect URI for RP $logout_rp"
+-                    );
+-                    $redirect_uri_allowed = 1;
+-                }
+-            }
+-        }
+-
+-        unless ($redirect_uri_allowed) {
++        unless (
++            $self->findRPFromUri(
++                $post_logout_redirect_uri,
++                'oidcRPMetaDataOptionsPostLogoutRedirectUris'
++            )
++          )
++        {
+             $self->logger->error("$post_logout_redirect_uri is not allowed");
+             return $self->p->login($req);
+         }
+@@ -2324,7 +2355,7 @@
+             claims_supported                 => [qw/sub iss auth_time acr/],
+             request_parameter_supported      => JSON::true,
+             request_uri_parameter_supported  => JSON::true,
+-            require_request_uri_registration => JSON::false,
++            require_request_uri_registration => JSON::true,
+             response_modes_supported => [ "query", "fragment", "form_post", ],
+ 
+             # Algorithms
+@@ -2375,19 +2406,7 @@
+         }
+     }
+ 
+-    # Extract request_uri/request parameter
+-    my $request = $req->param('request');
+-    if ( $req->param('request_uri') ) {
+-        $request = $self->getRequestJWT( $req->param('request_uri') );
+-    }
+-
+-    if ($request) {
+-        my $request_data = getJWTPayload($request);
+-        foreach ( keys %$request_data ) {
+-            $req->env->{ "llng_oidc_" . $_ } = $request_data->{$_};
+-        }
+-    }
+-
++    my $rp;
+     if ( $req->param('client_id') ) {
+         my $rp = $self->getRP( $req->param('client_id') );
+         $req->env->{"llng_oidc_rp"} = $rp if $rp;
+@@ -2399,6 +2418,27 @@
+           if $targetAuthnLevel;
+     }
+ 
++    # Extract request_uri/request parameter
++    my $request = $req->param('request');
++    if ( my $request_uri = $req->param('request_uri') ) {
++        if (
++            $rp
++            and $self->isUriAllowedForRP(
++                $request_uri, $rp, 'oidcRPMetaDataOptionsRequestUris', 1
++            )
++          )
++        {
++            $request = $self->getRequestJWT($request_uri);
++        }
++    }
++
++    if ($request) {
++        my $request_data = getJWTPayload($request);
++        foreach ( keys %$request_data ) {
++            $req->env->{ "llng_oidc_" . $_ } = $request_data->{$_};
++        }
++    }
++
+     return PE_OK;
+ }
+ 
+--- /dev/null
++++ b/lemonldap-ng-portal/t/32-OIDC-Request-Uri.t
+@@ -0,0 +1,200 @@
++use warnings;
++use lib 'inc';
++use Test::More;
++use strict;
++use IO::String;
++use LWP::UserAgent;
++use LWP::Protocol::PSGI;
++use Plack::Request;
++use Plack::Response;
++use MIME::Base64;
++
++# Initialization
++my ( $op, $res );
++ok( $op = op(), 'OP portal' );
++
++my $i = $op->p->loadedModules->{'Lemonldap::NG::Portal::Issuer::OpenIDConnect'};
++
++# Lazy load client
++#$i->getRP("rpid");
++
++our $call_allowed = 1;
++
++LWP::Protocol::PSGI->register(
++    sub {
++        my $req     = Plack::Request->new(@_);
++        my $payload = {
++            client_id    => "rpid",
++            redirect_uri => "http://redirect.uri/"
++        };
++
++        is( $req->uri->host, "request.uri", "only authorized URI is called" );
++        ok( $call_allowed, "Call is expected in this scenario" );
++
++        if ( $req->path_info eq "/baduri" ) {
++            $payload->{redirect_uri} = "http://invalid/";
++        }
++        if ( $req->path_info eq "/badclientid" ) {
++            $payload->{client_id} = "otherid";
++        }
++        my $res = Plack::Response->new(200);
++        $res->content_type('application/json');
++        $res->body( $i->createJWT( $payload, "HS256", "rp" ) );
++        return $res->finalize;
++    }
++);
++
++BEGIN {
++    require 't/test-lib.pm';
++    require 't/oidc-lib.pm';
++}
++
++my $debug = 'error';
++
++subtest "Successful request" => sub {
++    my $idpId = login( $op, "french" );
++    $res = authorize(
++        $op, $idpId,
++        {
++            response_type => "code",
++            client_id     => "rpid",
++            scope         => "openid",
++            state         => "xxyy",
++            request_uri   => "http://request.uri/valid"
++        }
++    );
++    expectRedirection( $res, qr,http://redirect.uri/.*state=xxyy.*, );
++};
++
++subtest "Successful request, override of bad redirect_uri" => sub {
++    my $idpId = login( $op, "french" );
++    $res = authorize(
++        $op, $idpId,
++        {
++            response_type => "code",
++            client_id     => "rpid",
++            scope         => "openid",
++            redirect_uri  => "http://bad.uri/",
++            request_uri   => "http://request.uri/valid"
++        }
++    );
++    expectRedirection( $res, qr,http://redirect.uri/.*, );
++};
++
++subtest "unauthorized Request URI" => sub {
++    my $idpId = login( $op, "french" );
++    local $call_allowed = 0;
++    $res = authorize(
++        $op, $idpId,
++        {
++            response_type => "code",
++            client_id     => "rpid",
++            scope         => "openid",
++            request_uri   => "http://bad.uri/"
++        }
++    );
++    expectPortalError( $res, 24 );
++};
++
++subtest "Allowed request URI, bad redirect URI" => sub {
++    my $idpId = login( $op, "french" );
++    $res = authorize(
++        $op, $idpId,
++        {
++            response_type => "code",
++            client_id     => "rpid",
++            scope         => "openid",
++            request_uri   => "http://request.uri/baduri"
++        }
++    );
++    expectPortalError( $res, 108 );
++};
++
++subtest "Allowed request URI, bad redirect URI override" => sub {
++    my $idpId = login( $op, "french" );
++    $res = authorize(
++        $op, $idpId,
++        {
++            response_type => "code",
++            client_id     => "rpid",
++            scope         => "openid",
++            redirect_uri  => "http://redirect.uri/",
++            request_uri   => "http://request.uri/baduri"
++        }
++    );
++    expectPortalError( $res, 108 );
++};
++
++subtest "Undeclared request_uri is not called before auth" => sub {
++    local $call_allowed = 0;
++    $res = authorize(
++        $op, undef,
++        {
++            response_type => "code",
++            client_id     => "rpid",
++            scope         => "openid",
++            request_uri   => "http://bad.uri/valid"
++        }
++    );
++
++    # LWP PSGI handler above will fail the test if a call is performed
++    ok(1);
++};
++
++clean_sessions();
++done_testing();
++
++sub op {
++    return LLNG::Manager::Test->new( {
++            ini => {
++                logLevel                        => $debug,
++                domain                          => 'idp.com',
++                portal                          => 'http://auth.op.com/',
++                authentication                  => 'Demo',
++                userDB                          => 'Same',
++                issuerDBOpenIDConnectActivation => 1,
++                oidcRPMetaDataExportedVars      => {
++                    rp => {
++                        email       => "mail",
++                        family_name => "cn",
++                        name        => "cn"
++                    }
++                },
++                oidcServiceAllowHybridFlow            => 1,
++                oidcServiceAllowImplicitFlow          => 1,
++                oidcServiceAllowAuthorizationCodeFlow => 1,
++                oidcRPMetaDataOptions                 => {
++                    rp => {
++                        oidcRPMetaDataOptionsDisplayName           => "RP",
++                        oidcRPMetaDataOptionsIDTokenExpiration     => 3600,
++                        oidcRPMetaDataOptionsClientID              => "rpid",
++                        oidcRPMetaDataOptionsClientSecret          => "rpid",
++                        oidcRPMetaDataOptionsIDTokenSignAlg        => "RS256",
++                        oidcRPMetaDataOptionsBypassConsent         => 0,
++                        oidcRPMetaDataOptionsUserIDAttr            => "",
++                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
++                        oidcRPMetaDataOptionsBypassConsent         => 1,
++                        oidcRPMetaDataOptionsRequestUris           =>
++                          "http://request.uri/*",
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          "http://redirect.uri/",
++                        oidcRPMetaDataOptionsPostLogoutRedirectUris =>
++                          "http://auth.rp.com/?logout=1"
++                    }
++                },
++                oidcOPMetaDataOptions           => {},
++                oidcOPMetaDataJSON              => {},
++                oidcOPMetaDataJWKS              => {},
++                oidcServiceMetaDataAuthnContext => {
++                    'loa-4' => 4,
++                    'loa-1' => 1,
++                    'loa-5' => 5,
++                    'loa-2' => 2,
++                    'loa-3' => 3
++                },
++                oidcServicePrivateKeySig => oidc_key_op_private_sig,
++                oidcServicePublicKeySig  => oidc_cert_op_public_sig,
++            }
++        }
++    );
++}
diff --git a/debian/patches/fix-open-redirection-without-OIDC-redirect-uris.patch b/debian/patches/fix-open-redirection-without-OIDC-redirect-uris.patch
new file mode 100644
index 000000000..a5c0c60ab
--- /dev/null
+++ b/debian/patches/fix-open-redirection-without-OIDC-redirect-uris.patch
@@ -0,0 +1,729 @@
+Description: Fix open redirection when OIDC RP has no oidcRPMetaDataOptionsRedirectUris
+ This issue concerns only people that modify config by hand. The manager
+ refuses already a relying party without redirect URIs.
+Author: Yadd <yadd at debian.org>
+Origin: upstream, commit:c1de35ad
+Bug: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/issues/3003
+Forwarded: not-needed
+Applied-Upstream: v2.17.1, commit:c1de35ad
+Reviewed-By: <name and email of someone who approved/reviewed the patch>
+Last-Update: 2023-09-20
+
+--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/OpenIDConnect.pm
++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Issuer/OpenIDConnect.pm
+@@ -19,6 +19,7 @@
+   PE_OIDC_SERVICE_NOT_ALLOWED
+   PE_FIRSTACCESS
+   PE_SENDRESPONSE
++  URIRE
+ );
+ use String::Random qw/random_string/;
+ 
+@@ -303,6 +304,12 @@
+                     return PE_UNAUTHORIZEDURL;
+                 }
+             }
++            elsif ($redirect_uri) {
++                $self->logger->error(
++"RP $rp has no RedirectUris, unable to handle accept redirect_uri=$redirect_uri"
++                );
++                return PE_UNAUTHORIZEDURL;
++            }
+ 
+             # Check if flow is allowed
+             if ( $flow eq "authorizationcode"
+--- a/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code-OP-logout.t
++++ b/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code-OP-logout.t
+@@ -222,6 +222,8 @@
+                           'http://auth.rp.com/oidc/logout',
+                         oidcRPMetaDataOptionsLogoutType            => 'front',
+                         oidcRPMetaDataOptionsLogoutSessionRequired => 0,
++                        oidcRPMetaDataOptionsRedirectUris          =>
++                          'http://auth.rp.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code-jwt-userinfo.t
++++ b/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code-jwt-userinfo.t
+@@ -333,7 +333,9 @@
+                         oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
+                         oidcRPMetaDataOptionsUserInfoSignAlg        => "HS512",
+                         oidcRPMetaDataOptionsPostLogoutRedirectUris =>
+-                          "http://auth.rp.com/?logout=1"
++                          "http://auth.rp.com/?logout=1",
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          'http://auth.rp.com/?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code-public_client.t
++++ b/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code-public_client.t
+@@ -332,7 +332,9 @@
+                         oidcRPMetaDataOptionsUserIDAttr             => "",
+                         oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
+                         oidcRPMetaDataOptionsPostLogoutRedirectUris =>
+-                          "http://auth.rp.com/?logout=1"
++                          "http://auth.rp.com/?logout=1",
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          'http://auth.rp.com/?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code-with-authchoice.t
++++ b/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code-with-authchoice.t
+@@ -286,7 +286,9 @@
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+                         oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
+                         oidcRPMetaDataOptionsPostLogoutRedirectUris =>
+-                          "http://auth.rp.com/?logout=1"
++                          "http://auth.rp.com/?logout=1",
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          'http://auth.rp.com/?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code-with-info.t
++++ b/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code-with-info.t
+@@ -336,7 +336,9 @@
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+                         oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
+                         oidcRPMetaDataOptionsPostLogoutRedirectUris =>
+-                          "http://auth.rp.com/?logout=1"
++                          "http://auth.rp.com/?logout=1",
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          'http://auth.rp.com/?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code-with-none-alg.t
++++ b/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code-with-none-alg.t
+@@ -328,7 +328,9 @@
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+                         oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
+                         oidcRPMetaDataOptionsPostLogoutRedirectUris =>
+-                          "http://auth.rp.com/?logout=1"
++                          "http://auth.rp.com/?logout=1",
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          'http://auth.rp.com/?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code.t
++++ b/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-authorization_code.t
+@@ -447,7 +447,9 @@
+                         oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
+                         oidcRPMetaDataOptionsPostLogoutRedirectUris =>
+                           "http://auth.rp.com/?logout=1",
+-                        oidcRPMetaDataOptionsRule => '$uid eq "french"',
++                        oidcRPMetaDataOptionsRule         => '$uid eq "french"',
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          'http://auth.rp.com/?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-hybrid.t
++++ b/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-hybrid.t
+@@ -248,7 +248,9 @@
+                         oidcRPMetaDataOptionsBypassConsent     => 1,
+                         oidcRPMetaDataOptionsClientSecret      => "rpsecret",
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+-                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600
++                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
++                        oidcRPMetaDataOptionsRedirectUris          =>
++                          'http://auth.rp.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-implicit-no-token.t
++++ b/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-implicit-no-token.t
+@@ -231,7 +231,9 @@
+                         oidcRPMetaDataOptionsBypassConsent     => 0,
+                         oidcRPMetaDataOptionsClientSecret      => "rpsecret",
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+-                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600
++                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
++                        oidcRPMetaDataOptionsRedirectUris          =>
++                          'http://auth.rp.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-implicit.t
++++ b/lemonldap-ng-portal/t/32-Auth-and-issuer-OIDC-implicit.t
+@@ -249,7 +249,9 @@
+                         oidcRPMetaDataOptionsBypassConsent     => 0,
+                         oidcRPMetaDataOptionsClientSecret      => "rpsecret",
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+-                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600
++                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
++                        oidcRPMetaDataOptionsRedirectUris          =>
++                          'http://auth.rp.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-OIDC-Code-Flow-with-2F-UpgradeOnly.t
++++ b/lemonldap-ng-portal/t/32-OIDC-Code-Flow-with-2F-UpgradeOnly.t
+@@ -356,7 +356,9 @@
+                         oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
+                         oidcRPMetaDataOptionsAuthnLevel             => 5,
+                         oidcRPMetaDataOptionsPostLogoutRedirectUris =>
+-                          "http://auth.rp.com/?logout=1"
++                          "http://auth.rp.com/?logout=1",
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          'http://auth.rp.com/?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-OIDC-Code-Flow-with-2F.t
++++ b/lemonldap-ng-portal/t/32-OIDC-Code-Flow-with-2F.t
+@@ -356,7 +356,9 @@
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+                         oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
+                         oidcRPMetaDataOptionsPostLogoutRedirectUris =>
+-                          "http://auth.rp.com/?logout=1"
++                          "http://auth.rp.com/?logout=1",
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          'http://auth.rp.com/?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-OIDC-Hooks.t
++++ b/lemonldap-ng-portal/t/32-OIDC-Hooks.t
+@@ -51,6 +51,7 @@
+                     oidcRPMetaDataOptionsBypassConsent         => 1,
+                     oidcRPMetaDataOptionsRefreshToken          => 1,
+                     oidcRPMetaDataOptionsAllowOffline          => 1,
++                    oidcRPMetaDataOptionsRedirectUris => 'http://rp2.com/',
+                 },
+                 oauth => {
+                     oidcRPMetaDataOptionsDisplayName  => "oauth",
+--- a/lemonldap-ng-portal/t/32-OIDC-Logout-from-RP-bypass-confirm.t
++++ b/lemonldap-ng-portal/t/32-OIDC-Logout-from-RP-bypass-confirm.t
+@@ -254,6 +254,8 @@
+                         oidcRPMetaDataOptionsLogoutBypassConfirm    => 1,
+                         oidcRPMetaDataOptionsPostLogoutRedirectUris =>
+                           "http://auth.rp.com?logout=1",
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          'http://auth.rp.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-OIDC-Logout-redirect-uri-not-allowed.t
++++ b/lemonldap-ng-portal/t/32-OIDC-Logout-redirect-uri-not-allowed.t
+@@ -240,6 +240,8 @@
+                         oidcRPMetaDataOptionsLogoutBypassConfirm    => 0,
+                         oidcRPMetaDataOptionsPostLogoutRedirectUris =>
+                           "http://auth.rpother.com?logout=1",
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          'http://auth.rp.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-OIDC-Macro.t
++++ b/lemonldap-ng-portal/t/32-OIDC-Macro.t
+@@ -129,6 +129,7 @@
+                         oidcRPMetaDataOptionsClientSecret      => "rpid",
+                         oidcRPMetaDataOptionsUserIDAttr        => "custom_sub",
+                         oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
++                        oidcRPMetaDataOptionsRedirectUris => 'http://rp.com/',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/32-OIDC-Offline-Session.t
++++ b/lemonldap-ng-portal/t/32-OIDC-Offline-Session.t
+@@ -201,7 +201,7 @@
+                 oidcRPMetaDataOptionsIDTokenForceClaims  => 1,
+                 oidcRPMetaDataOptionsAdditionalAudiences =>
+                   "http://my.extra.audience/test urn:extra2",
+-
++                oidcRPMetaDataOptionsRedirectUris => 'http://test/',
+             }
+         },
+         oidcServicePrivateKeySig => oidc_key_op_private_sig,
+--- a/lemonldap-ng-portal/t/32-OIDC-Refresh-Token.t
++++ b/lemonldap-ng-portal/t/32-OIDC-Refresh-Token.t
+@@ -158,6 +158,7 @@
+                 oidcRPMetaDataOptionsIDTokenForceClaims  => 1,
+                 oidcRPMetaDataOptionsAdditionalAudiences =>
+                   "http://my.extra.audience/test urn:extra2",
++                oidcRPMetaDataOptionsRedirectUris => 'http://test/',
+             }
+         },
+         oidcServicePrivateKeySig => oidc_key_op_private_sig,
+--- a/lemonldap-ng-portal/t/32-OIDC-Token-Exchange.t
++++ b/lemonldap-ng-portal/t/32-OIDC-Token-Exchange.t
+@@ -50,6 +50,7 @@
+                     oidcRPMetaDataOptionsBypassConsent         => 1,
+                     oidcRPMetaDataOptionsRefreshToken          => 1,
+                     oidcRPMetaDataOptionsIDTokenForceClaims    => 1,
++                    oidcRPMetaDataOptionsRedirectUris          => 'http://test',
+                 },
+             },
+             oidcRPMetaDataScopeRules => {
+--- a/lemonldap-ng-portal/t/32-OIDC-Token-Introspection.t
++++ b/lemonldap-ng-portal/t/32-OIDC-Token-Introspection.t
+@@ -61,6 +61,7 @@
+                     oidcRPMetaDataOptionsUserIDAttr            => "",
+                     oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
+                     oidcRPMetaDataOptionsBypassConsent         => 1,
++                    oidcRPMetaDataOptionsRedirectUris => 'http://rp2.com/',
+                 },
+                 oauth => {
+                     oidcRPMetaDataOptionsDisplayName  => "oauth",
+--- a/lemonldap-ng-portal/t/32-OIDC-Token-Security.t
++++ b/lemonldap-ng-portal/t/32-OIDC-Token-Security.t
+@@ -51,6 +51,7 @@
+                     oidcRPMetaDataOptionsUserIDAttr            => "",
+                     oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
+                     oidcRPMetaDataOptionsBypassConsent         => 1,
++                    oidcRPMetaDataOptionsRedirectUris => 'http://rp.com/',
+                 },
+                 rp2 => {
+                     oidcRPMetaDataOptionsDisplayName           => "RP2",
+@@ -61,7 +62,8 @@
+                     oidcRPMetaDataOptionsUserIDAttr            => "",
+                     oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
+                     oidcRPMetaDataOptionsBypassConsent         => 1,
+-                    oidcRPMetaDataOptionsRule => '$uid eq "dwho"',
++                    oidcRPMetaDataOptionsRule         => '$uid eq "dwho"',
++                    oidcRPMetaDataOptionsRedirectUris => 'http://rp2.com/',
+                 }
+             },
+             oidcOPMetaDataOptions           => {},
+@@ -98,7 +100,7 @@
+ 
+ # Try to get code for RP1 with invalide scope name
+ $query =
+-"response_type=code&scope=openid%20profile%20email%22&client_id=rpid&state=af0ifjsldkj&redirect_uri=http%3A%2F%2Frp2.com%2F";
++"response_type=code&scope=openid%20profile%20email%22&client_id=rpid&state=af0ifjsldkj&redirect_uri=http%3A%2F%2Frp.com%2F";
+ ok(
+     $res = $op->_get(
+         "/oauth2/authorize",
+@@ -113,7 +115,7 @@
+ #
+ # Get code for RP1
+ $query =
+-"response_type=code&scope=openid%20profile%20email&client_id=rpid&state=af0ifjsldkj&redirect_uri=http%3A%2F%2Frp2.com%2F";
++"response_type=code&scope=openid%20profile%20email&client_id=rpid&state=af0ifjsldkj&redirect_uri=http%3A%2F%2Frp.com%2F";
+ ok(
+     $res = $op->_get(
+         "/oauth2/authorize",
+@@ -125,7 +127,7 @@
+ );
+ count(1);
+ 
+-my ($code) = expectRedirection( $res, qr#http://rp2\.com/.*code=([^\&]*)# );
++my ($code) = expectRedirection( $res, qr#http://rp\.com/.*code=([^\&]*)# );
+ 
+ # Play code on RP2
+ $query = buildForm(
+--- /dev/null
++++ b/lemonldap-ng-portal/t/32-OIDC-redirect_uri-filter.t
+@@ -0,0 +1,252 @@
++use warnings;
++use lib 'inc';
++use Test::More;
++use strict;
++use IO::String;
++use Lemonldap::NG::Common::FormEncode;
++use LWP::UserAgent;
++use LWP::Protocol::PSGI;
++use MIME::Base64;
++use URI::QueryParam;
++
++BEGIN {
++    require 't/test-lib.pm';
++    require 't/oidc-lib.pm';
++}
++
++my @badUrls = ( 'http://attacker_url.com/requesturi.jwt', );
++
++my $debug = 'error';
++my ( $op, $rp, $res );
++
++my $access_token;
++
++LWP::Protocol::PSGI->register(
++    sub {
++        my $req = Plack::Request->new(@_);
++        ok( $req->uri =~ m#http://auth.((?:o|r)p).com(.*)#, ' REST request' );
++        my $host = $1;
++        my $url  = $2;
++        my ( $res, $client );
++        count(1);
++        if ( $host eq 'op' ) {
++            pass("  Request from RP to OP,     endpoint $url");
++            $client = $op;
++        }
++        elsif ( $host eq 'rp' ) {
++            pass('  Request from OP to RP');
++            $client = $rp;
++        }
++        else {
++            fail('  Aborting REST request (external)');
++            return [ 500, [], [] ];
++        }
++        if ( $req->method =~ /^post$/i ) {
++            my $s = $req->content;
++            if ( $req->uri eq '/token/oauth2' ) {
++                is( $req->param("my_param"),
++                    "my value", "oidcGenerateTokenRequest called" );
++                count(1);
++            }
++            ok(
++                $res = $client->_post(
++                    $url, IO::String->new($s),
++                    length => length($s),
++                    type   => $req->header('Content-Type'),
++                ),
++                '  Execute request'
++            );
++        }
++        else {
++            ok(
++                $res = $client->_get(
++                    $url,
++                    custom => {
++                        HTTP_AUTHORIZATION => $req->header('Authorization'),
++                    }
++                ),
++                '  Execute request'
++            );
++        }
++        ok( $res->[0] == 200, '  Response is 200' );
++        ok( getHeader( $res, 'Content-Type' ) =~ m#^application/json#,
++            '  Content is JSON' )
++          or explain( $res->[1], 'Content-Type => application/json' );
++        count(4);
++        if ( $res->[2]->[0] =~ /"access_token":"(.*?)"/ ) {
++            $access_token = $1;
++            pass "Found access_token $access_token";
++            count(1);
++        }
++        return $res;
++    }
++);
++
++# Initialization
++ok( $op = op(), 'OP portal' );
++
++ok( $res = $op->_get('/oauth2/jwks'), 'Get JWKS,     endpoint /oauth2/jwks' );
++expectOK($res);
++my $jwks = $res->[2]->[0];
++
++ok(
++    $res = $op->_get('/.well-known/openid-configuration'),
++    'Get metadata, endpoint /.well-known/openid-configuration'
++);
++expectOK($res);
++my $metadata = $res->[2]->[0];
++count(3);
++
++switch ('rp');
++&Lemonldap::NG::Handler::Main::cfgNum( 0, 0 );
++ok( $rp = rp( $jwks, $metadata ), 'RP portal' );
++count(1);
++
++# Authentication
++switch ('op');
++my $query = "user=french&password=french";
++ok(
++    $res = $op->_post(
++        '/',,
++        IO::String->new($query),
++        accept => 'text/html',
++        length => length($query),
++    ),
++    "Post authentication"
++);
++count(1);
++my $idpId = expectCookie($res);
++
++# Query RP for auth
++switch ('rp');
++ok( $res = $rp->_get( '/', accept => 'text/html' ), 'Unauth SP request' );
++count(1);
++my $url;
++( $url, $query ) =
++  expectRedirection( $res, qr#http://auth.op.com(/oauth2/authorize)\?(.*)$# );
++
++# MAIN PART OF TEST
++switch ('op');
++foreach my $badUrl (@badUrls) {
++    my $badArg = build_urlencoded( redirect_uri => $badUrl );
++    my $forged = $query;
++    $forged =~ s#redirect_uri=(?:[^&]*)#$badArg#;
++
++    ok(
++        $res = $op->_get(
++            $url,
++            query  => $forged,
++            accept => 'text/html',
++            cookie => "lemonldap=$idpId",
++        ),
++        "Push bad request to OP"
++    );
++    expectOK($res);
++    ok( $res->[2][0] =~ /trmsg="108"/, 'Get unauthorized redirect_uri' );
++    count(2);
++}
++
++clean_sessions();
++done_testing( count() );
++
++sub op {
++    return LLNG::Manager::Test->new( {
++            ini => {
++                logLevel                        => $debug,
++                domain                          => 'idp.com',
++                portal                          => 'http://auth.op.com/',
++                authentication                  => 'Demo',
++                userDB                          => 'Same',
++                issuerDBOpenIDConnectActivation => "1",
++                restSessionServer               => 1,
++                restExportSecretKeys            => 1,
++                oidcRPMetaDataExportedVars      => {
++                    rp => {
++                        email       => "mail",
++                        family_name => "cn",
++                        name        => "cn"
++                    }
++                },
++                oidcServiceAllowHybridFlow            => 1,
++                oidcServiceAllowImplicitFlow          => 1,
++                oidcServiceAllowAuthorizationCodeFlow => 1,
++                oidcRPMetaDataOptions                 => {
++                    rp => {
++                        oidcRPMetaDataOptionsDisplayName       => "RP",
++                        oidcRPMetaDataOptionsIDTokenExpiration => 3600,
++                        oidcRPMetaDataOptionsClientID          => "rpid",
++                        oidcRPMetaDataOptionsIDTokenSignAlg    => "HS512",
++                        oidcRPMetaDataOptionsBypassConsent     => 0,
++                        oidcRPMetaDataOptionsClientSecret      => "rpsecret",
++                        oidcRPMetaDataOptionsRefreshToken      => 1,
++                        oidcRPMetaDataOptionsUserIDAttr        => "",
++                        oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
++                        oidcRPMetaDataOptionsPostLogoutRedirectUris =>
++                          "http://auth.rp.com/?logout=1",
++                        oidcRPMetaDataOptionsRule => '$uid eq "french"',
++                    }
++                },
++                oidcOPMetaDataOptions           => {},
++                oidcOPMetaDataJSON              => {},
++                oidcOPMetaDataJWKS              => {},
++                oidcServiceMetaDataAuthnContext => {
++                    'loa-4'       => 4,
++                    'customacr-1' => 1,
++                    'loa-5'       => 5,
++                    'loa-2'       => 2,
++                    'loa-3'       => 3
++                },
++                oidcServicePrivateKeySig => oidc_key_op_private_sig,
++                oidcServicePublicKeySig  => oidc_cert_op_public_sig,
++            }
++        }
++    );
++}
++
++sub rp {
++    my ( $jwks, $metadata ) = @_;
++    return LLNG::Manager::Test->new( {
++            ini => {
++                logLevel                   => $debug,
++                domain                     => 'rp.com',
++                portal                     => 'http://auth.rp.com/',
++                authentication             => 'OpenIDConnect',
++                userDB                     => 'Same',
++                restSessionServer          => 1,
++                restExportSecretKeys       => 1,
++                oidcOPMetaDataExportedVars => {
++                    op => {
++                        cn   => "name",
++                        uid  => "sub",
++                        sn   => "family_name",
++                        mail => "email"
++                    }
++                },
++                oidcOPMetaDataOptions => {
++                    op => {
++                        oidcOPMetaDataOptionsCheckJWTSignature => 1,
++                        oidcOPMetaDataOptionsJWKSTimeout       => 0,
++                        oidcOPMetaDataOptionsAcrValues => "loa-32 customacr-1",
++                        oidcOPMetaDataOptionsClientSecret => "rpsecret",
++                        oidcOPMetaDataOptionsScope => "openid profile email",
++                        oidcOPMetaDataOptionsStoreIDToken     => 0,
++                        oidcOPMetaDataOptionsMaxAge           => 30,
++                        oidcOPMetaDataOptionsDisplay          => "",
++                        oidcOPMetaDataOptionsClientID         => "rpid",
++                        oidcOPMetaDataOptionsStoreIDToken     => 1,
++                        oidcOPMetaDataOptionsUseNonce         => 1,
++                        oidcOPMetaDataOptionsConfigurationURI =>
++                          "https://auth.op.com/.well-known/openid-configuration"
++                    }
++                },
++                oidcOPMetaDataJWKS => {
++                    op => $jwks,
++                },
++                oidcOPMetaDataJSON => {
++                    op => $metadata,
++                },
++                customPlugins => 't::OidcHookPlugin',
++            }
++        }
++    );
++}
+--- a/lemonldap-ng-portal/t/37-Issuer-Timeout.t
++++ b/lemonldap-ng-portal/t/37-Issuer-Timeout.t
+@@ -178,7 +178,9 @@
+                         oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
+                         oidcRPMetaDataOptionsBypassConsent          => 1,
+                         oidcRPMetaDataOptionsPostLogoutRedirectUris =>
+-                          "http://auth.rp.com/?logout=1"
++                          "http://auth.rp.com/?logout=1",
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          'http://rp.example.com/',
+                     },
+                     rp2 => {
+                         oidcRPMetaDataOptionsDisplayName       => "RP",
+@@ -191,7 +193,9 @@
+                         oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
+                         oidcRPMetaDataOptionsBypassConsent          => 1,
+                         oidcRPMetaDataOptionsPostLogoutRedirectUris =>
+-                          "http://auth.rp2.com/?logout=1"
++                          "http://auth.rp2.com/?logout=1",
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          'http://rp2.example.com/',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/37-Logout-from-OIDC-RP-to-SAML-IDP-Redirect.t
++++ b/lemonldap-ng-portal/t/37-Logout-from-OIDC-RP-to-SAML-IDP-Redirect.t
+@@ -335,7 +335,9 @@
+                         oidcRPMetaDataOptionsBypassConsent     => 1,
+                         oidcRPMetaDataOptionsClientSecret      => "rpsecret",
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+-                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600
++                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
++                        oidcRPMetaDataOptionsRedirectUris          =>
++                          'http://auth.rp.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/37-Logout-from-OIDC-RP-to-SAML-IDP-SOAP.t
++++ b/lemonldap-ng-portal/t/37-Logout-from-OIDC-RP-to-SAML-IDP-SOAP.t
+@@ -339,6 +339,8 @@
+                         oidcRPMetaDataOptionsAccessTokenExpiration  => 3600,
+                         oidcRPMetaDataOptionsPostLogoutRedirectUris =>
+                           'http://auth.rp.com?logout=1',
++                        oidcRPMetaDataOptionsRedirectUris =>
++                          'http://auth.rp.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/37-Logout-from-OIDC-RP-to-SAML-SP.t
++++ b/lemonldap-ng-portal/t/37-Logout-from-OIDC-RP-to-SAML-SP.t
+@@ -350,7 +350,9 @@
+                         oidcRPMetaDataOptionsBypassConsent     => 0,
+                         oidcRPMetaDataOptionsClientSecret      => "rpsecret",
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+-                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600
++                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
++                        oidcRPMetaDataOptionsRedirectUris          =>
++                          'http://auth.rp.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/37-OIDC-RP-to-SAML-IdP-GET-with-WAYF.t
++++ b/lemonldap-ng-portal/t/37-OIDC-RP-to-SAML-IdP-GET-with-WAYF.t
+@@ -373,7 +373,9 @@
+                         oidcRPMetaDataOptionsBypassConsent     => 0,
+                         oidcRPMetaDataOptionsClientSecret      => "rpsecret",
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+-                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600
++                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
++                        oidcRPMetaDataOptionsRedirectUris          =>
++                          'http://auth.rp.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/37-OIDC-RP-to-SAML-IdP-GET.t
++++ b/lemonldap-ng-portal/t/37-OIDC-RP-to-SAML-IdP-GET.t
+@@ -353,7 +353,9 @@
+                         oidcRPMetaDataOptionsBypassConsent     => 0,
+                         oidcRPMetaDataOptionsClientSecret      => "rpsecret",
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+-                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600
++                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
++                        oidcRPMetaDataOptionsRedirectUris          =>
++                          'http://auth.rp.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/37-OIDC-RP-to-SAML-IdP-POST.t
++++ b/lemonldap-ng-portal/t/37-OIDC-RP-to-SAML-IdP-POST.t
+@@ -355,7 +355,9 @@
+                         oidcRPMetaDataOptionsBypassConsent     => 0,
+                         oidcRPMetaDataOptionsClientSecret      => "rpsecret",
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+-                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600
++                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
++                        oidcRPMetaDataOptionsRedirectUris          =>
++                          'http://auth.rp.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/37-SAML-SP-GET-to-OIDC-OP.t
++++ b/lemonldap-ng-portal/t/37-SAML-SP-GET-to-OIDC-OP.t
+@@ -289,7 +289,9 @@
+                         oidcRPMetaDataOptionsBypassConsent     => 0,
+                         oidcRPMetaDataOptionsClientSecret      => "rpsecret",
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+-                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600
++                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
++                        oidcRPMetaDataOptionsRedirectUris          =>
++                          'http://auth.proxy.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+--- a/lemonldap-ng-portal/t/37-SAML-SP-POST-to-OIDC-OP.t
++++ b/lemonldap-ng-portal/t/37-SAML-SP-POST-to-OIDC-OP.t
+@@ -255,8 +255,7 @@
+ done_testing( count() );
+ 
+ sub op {
+-    return LLNG::Manager::Test->new(
+-        {
++    return LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel                        => $debug,
+                 domain                          => 'op.com',
+@@ -288,7 +287,9 @@
+                         oidcRPMetaDataOptionsBypassConsent     => 0,
+                         oidcRPMetaDataOptionsClientSecret      => "rpsecret",
+                         oidcRPMetaDataOptionsUserIDAttr        => "",
+-                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600
++                        oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
++                        oidcRPMetaDataOptionsRedirectUris          =>
++                          'http://auth.proxy.com?openidconnectcallback=1',
+                     }
+                 },
+                 oidcOPMetaDataOptions           => {},
+@@ -310,8 +311,7 @@
+ 
+ sub proxy {
+     my ( $jwks, $metadata ) = @_;
+-    return LLNG::Manager::Test->new(
+-        {
++    return LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel                   => $debug,
+                 domain                     => 'proxy.com',
+@@ -382,8 +382,7 @@
+ }
+ 
+ sub sp {
+-    return LLNG::Manager::Test->new(
+-        {
++    return LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel                          => $debug,
+                 domain                            => 'sp.com',
diff --git a/debian/patches/series b/debian/patches/series
index 14369dfd8..e4acf948c 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -7,3 +7,5 @@ fix-OP-acr-parsing.patch
 fix-viewer-endpoint.patch
 apply-user-control-to-authslave.patch
 fix-open-redirection.patch
+fix-open-redirection-without-OIDC-redirect-uris.patch
+SSRF-issue.patch


More information about the pkg-perl-maintainers mailing list