1111use SimpleSAML \Logger ;
1212use SimpleSAML \Module ;
1313use SimpleSAML \Module \casserver \Cas \Factories \TicketFactory ;
14+ use SimpleSAML \Module \casserver \Cas \ServiceValidator ;
1415use SimpleSAML \Module \casserver \Cas \Ticket \TicketStore ;
1516use SimpleSAML \Module \casserver \Controller \Traits \UrlTrait ;
1617use SimpleSAML \Session ;
1718use SimpleSAML \Utils ;
19+ use SimpleSAML \XHTML \Template ;
1820use Symfony \Component \HttpFoundation \Request ;
1921use Symfony \Component \HttpKernel \Attribute \AsController ;
2022use Symfony \Component \HttpKernel \Attribute \MapQueryParameter ;
@@ -41,6 +43,7 @@ class LogoutController
4143
4244 /** @var \SimpleSAML\Module\casserver\Cas\Ticket\TicketStore */
4345 protected TicketStore $ ticketStore ;
46+ private ServiceValidator $ serviceValidator ;
4447
4548
4649 /**
@@ -63,6 +66,8 @@ public function __construct(
6366 // argument in order to facilitate testin.
6467 $ this ->casConfig = ($ casConfig === null || $ casConfig === $ sspConfig )
6568 ? Configuration::getConfig ('module_casserver.php ' ) : $ casConfig ;
69+ $ this ->serviceValidator = new ServiceValidator ($ this ->casConfig );
70+
6671 $ this ->authSource = $ source ?? new Simple ($ this ->casConfig ->getValue ('authsource ' ));
6772 $ this ->httpUtils = $ httpUtils ?? new Utils \HTTP ();
6873
@@ -82,49 +87,68 @@ public function __construct(
8287 * @param \Symfony\Component\HttpFoundation\Request $request
8388 * @param string|null $url
8489 *
85- * @return \SimpleSAML\HTTP\RunnableResponse
90+ * @return \SimpleSAML\XHTML\Template|\SimpleSAML\ HTTP\RunnableResponse
8691 */
8792 public function logout (
8893 Request $ request ,
8994 #[MapQueryParameter] ?string $ url = null ,
90- ): RunnableResponse {
95+ #[MapQueryParameter] ?string $ service = null ,
96+ ): Template |RunnableResponse {
9197 if (!$ this ->casConfig ->getOptionalValue ('enable_logout ' , false )) {
9298 $ this ->handleExceptionThrown ('Logout not allowed ' );
9399 }
94100
95- // Skip Logout Page configuration
96- $ skipLogoutPage = $ this ->casConfig ->getOptionalValue ('skip_logout_page ' , false );
97-
98- if ($ skipLogoutPage && $ url === null ) {
99- $ this ->handleExceptionThrown ('Required URL query parameter [url] not provided. (CAS Server) ' );
101+ // note: casv3 says to ignore the casv2 url parameter, however deployments will see a mix of cas v2 and
102+ // cas v3 clients so we support both. casv3 makes a query parameter optional
103+ $ isCasV3 = empty ($ url );
104+ $ url = $ isCasV3 ? $ service : $ url ;
105+
106+ // Validate the return $url is valid
107+ if (!is_null ($ url )) {
108+ $ isValidReturnUrl = !is_null ($ this ->serviceValidator ->checkServiceURL ($ this ->sanitize ($ url )));
109+ if (!$ isValidReturnUrl ) {
110+ try {
111+ $ url = $ this ->httpUtils ->checkURLAllowed ($ url );
112+ $ isValidReturnUrl = true ;
113+ } catch (\Exception $ e ) {
114+ Logger::info ('Invalid cas logout url ' . $ e ->getMessage ());
115+ $ isValidReturnUrl = false ;
116+ }
117+ }
118+ if (!$ isValidReturnUrl ) {
119+ // Protocol does not define behavior if invalid logout url sent
120+ // act like no url sent and show logout page
121+ Logger::info ("Invalid logout url ' $ url'. Ignoring " );
122+ $ url = null ;
123+ }
100124 }
101125
102- // Construct the logout redirect url
103- if ($ skipLogoutPage ) {
104- $ logoutRedirectUrl = $ url ;
105- $ params = [];
106- } else {
107- $ logoutRedirectUrl = Module::getModuleURL ('casserver/loggedOut ' );
108- $ params = $ url === null ? []
109- : ['url ' => $ url ];
110- }
126+ // Skip Logout Page configuration
127+ $ skipLogoutPage = !is_null ($ url ) && ($ isCasV3 || $ this ->casConfig ->getOptionalValue ('skip_logout_page ' , false ));
128+
111129
112130 // Delete the ticket from the session
113131 $ session = $ this ->getSession ();
114132 if ($ session !== null ) {
115133 $ this ->ticketStore ->deleteTicket ($ session ->getSessionId ());
116134 }
117135
118- // Redirect
119- if (!$ this ->authSource ->isAuthenticated ()) {
120- return new RunnableResponse ([$ this ->httpUtils , 'redirectTrustedURL ' ], [$ logoutRedirectUrl , $ params ]);
136+ if ($ this ->authSource ->isAuthenticated ()) {
137+ // Logout and come back here to handle the logout
138+ return new RunnableResponse (
139+ [$ this ->authSource , 'logout ' ],
140+ [$ this ->httpUtils ->getSelfURL ()],
141+ );
142+ } elseif ($ skipLogoutPage ) {
143+ $ params = [];
144+ return new RunnableResponse ([$ this ->httpUtils , 'redirectTrustedURL ' ], [$ url , $ params ]);
145+ } else {
146+ $ t = new Template ($ this ->sspConfig , 'casserver:loggedOut.twig ' );
147+ if ($ url ) {
148+ $ t ->data ['url ' ] = $ url ;
149+ }
150+ return $ t ;
121151 }
122-
123- // Logout and redirect
124- return new RunnableResponse (
125- [$ this ->authSource , 'logout ' ],
126- [$ logoutRedirectUrl ],
127- );
128152 }
129153
130154 /**
0 commit comments