[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1053219: marked as done (bookworm-pu: package lemonldap-ng/2.16.1+ds-deb12u2)



Your message dated Sat, 07 Oct 2023 09:59:43 +0000
with message-id <E1qp463-00A4Jb-Of@coccia.debian.org>
and subject line Released with 12.2
has caused the Debian Bug report #1053219,
regarding bookworm-pu: package lemonldap-ng/2.16.1+ds-deb12u2
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1053219: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1053219
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: bookworm
User: release.debian.org@packages.debian.org
Usertags: pu
X-Debbugs-Cc: lemonldap-ng@packages.debian.org, yadd@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
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@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@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@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@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@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

--- End Message ---
--- Begin Message ---
Version: 12.2

The upload requested in this bug has been released as part of 12.2.

--- End Message ---

Reply to: